<?php

defined('BASEPATH') or die('forbidden');

/**
 * 数据库类
 * @author  Karson
 * @name    database.php
 * @since   2013-4-3 15:36:16
 */
class Database {

    var $host;   //服务器
    var $user;   //用户名
    var $password; //密码
    var $name;   //数据库
    var $prefix;  //表前缀
    var $pconnect; //持久连接
    var $linkid;  //资源符
    var $sql;    //SQL语句
    var $result;  //执行结果资源符
    var $cache;   //SQL(SELECT)结果缓存
    var $querycounts; //SQL语句执行次数
    var $queryseconds; //SQL语句总耗时
    var $safequery; //是否启用安全查询
    var $debug;   //调试模式
    var $debuginfo; //调试信息

    function __construct($db = null) {
        if (empty($db))
            $this->error("未配置数据库");
        $this->host = $db['host'];
        $this->user = $db['user'];
        $this->password = $db['password'];
        $this->name = $db['name'];
        $this->prefix = $db['prefix'];
        $this->pconnect = isset($db['pconnect']) ? $db['pconnect'] : false;
        $this->debug = isset($db['debug']) ? $db['debug'] : false;
        $this->safequery = isset($db['safequery']) ? $db['safequery'] : true;
        $this->debuginfo = '';
        $this->linkid = 0;
        $this->querycounts = 0;
        $this->queryseconds = 0;
        $this->connect();
    }

    /**
     * 连接数据库
     */
    function connect() {
        $this->linkid = $this->pconnect ? mysql_pconnect($this->host, $this->user, $this->password) : mysql_connect($this->host, $this->user, $this->password);
        if (!$this->linkid) {
            $this->error(mysql_error());
        } else {
            $this->select_db();
        }
    }

    /**
     * 选择数据库
     * @param string $name 数据库名称，默认为null，即配置中的数据库名称
     */
    function select_db($name = null) {
        if (!is_null($name)) {
            $this->name = $name;
        }
        if (!mysql_select_db($this->name)) {
            $this->error(mysql_error());
        }
        mysql_query("SET NAMES 'utf8'");
    }

    /**
     * 判断是否存在表
     * @param string $name 表名
     * @return bool
     */
    function is_table($name) {
        $this->result[0] = mysql_list_tables($this->name, $this->linkid);
        while ($row = mysql_fetch_array($this->result[0])) {
            if (strtolower($row[0]) == strtolower($name)) {
                mysql_freeresult($this->result[0]);
                return true;
            }
        }
        mysql_freeresult($this->result[0]);
        return false;
    }

    /**
     * 执行一个普通SQL语句
     * @param string $sql 需要执行的SQL语句，用于执行普通查询
     * @param string $id SQL语句标识，用于区别于执行的其它SQL语句
     * @return bool 失败时返回false
     */
    function execute($sql, $id = 'default') {
        return $this->query($sql, $id);
    }

    /**
     * 执行一个普通SQL语句
     * @param string $sql 需要执行的SQL语句，用于执行普通查询
     * @param string $id SQL语句标识，用于区别于执行的其它SQL语句
     * @return bool 失败时返回false
     */
    function query($sql, $id = 'default') {
        $this->escape($sql);
        $sql_type = substr(strtolower($this->sql), 0, 6);
        $this->querycounts++;
        set_break('database');
        $this->result[$id] = mysql_query($this->sql, $this->linkid);
        $debugtime = get_break('database');
        $this->queryseconds += $debugtime;
        //耗时超过0.3秒的写入日志
        if ($debugtime > 0.3) {
            debug("需优化SQL语句,耗时:{$debugtime},SQL:" . $this->sql);
        }
        if ($this->debug) {
            $colorsql = $this->colorsql($this->sql);
            $affectdrows = $sql_type != 'select' ? $this->affected_rows() : 0;
            $this->debuginfo .= "<hr style='border:none;border-bottom:1px solid #aaa;height:1px;'/>";
            $this->debuginfo .= "[标识] => {$this->result[$id]}<br />[SQL] => {$colorsql}<br />[影响] => {$affectdrows} 条<br />[时间] => {$debugtime}";
        }
        if (!$this->result[$id]) {
            $this->error(mysql_error());
        } else {
            return true;
        }
    }

    /**
     * 执行一个只返回一条数据的SQL语句
     * @param string $sql 需要执行的SQL语句
     * @param constant $type 返回结果类型，默认为MYSQL_ASSOC,可接受MYSQL_BOTH，MYSQL_NUM
     * @return mixed 失败时返回null
     */
    function one($sql = '', $type = MYSQL_ASSOC) {
        if (stripos(strtolower($sql), "limit") === false)
            $sql = preg_replace("/([;])/", '', trim($sql)) . " LIMIT 0,1";
        $this->sql = $sql;
        $this->query($this->sql, 'one');
        $row = $this->get_array('one', $type);
        $this->free('one');
        return is_array($row) ? $row : null;
    }

    /**
     * 获取一个SELECT查询结果的所有数据
     * @param string $id SQL语句执行时的标识
     * @param constant $type 返回结果类型，默认为MYSQL_ASSOC,可接受MYSQL_BOTH，MYSQL_NUM
     * @param int $limit 最多返回的数据量，避免内存溢出
     * @return mixed 成功时返回二维数组，失败时返回false
     */
    function row($id = 'default', $type = MYSQL_ASSOC, $limit = 1000) {
        if ($this->result[$id] == 0) {
            return false;
        } else {
            $row = array();
            $i = 0;
            while ($rs = $this->get_array($id, $type)) {
                if ($i >= $limit)
                    break;
                $i++;
                $row[] = $rs;
            }
            $this->free($id);
            return $row;
        }
    }

    /**
     * 插入数据
     * @param string $table 表名
     * @param array $value 数据，格式为：array('filed1'=>'value1')
     * @param bool $replace 就否采用覆写模式，默认为false，可选true，选true时将会删除源记录
     * @return mixed 失败时返回false,成功返回插入的ID
     */
    function insert($table, $value = array(), $replace = false) {
        if (count($value) >= 1) {
            $fields = $values = array();
            foreach ($value as $field => $data) {
                $fields[] = $field;
                $values[] = $data;
            }
            $insert = $replace ? 'REPLACE' : 'INSERT';
            $sql = "{$insert} INTO {$table} (`" . implode('`, `', $fields) . "`) VALUES('" . implode("', '", $values) . "')";
            $this->query($sql, "insert");
            return $this->insert_id();
        } else {
            return false;
        }
    }

    /**
     * 删除数据
     * @param string $table 表名
     * @param mixed $where 条件，格式为：array('filed1'=>'value1')或"filed='value1'"
     * @param string $connector 连接符，默认为AND，可选AND或OR，该值只对$where为数组时有效
     * @return mixed 失败时返回false,成功返回影响的条数
     */
    function delete($table, $where = array(), $connector = 'AND') {
        if (count($where) >= 1) {
            $wherestr = array();
            if (is_array($where)) {
                foreach ($where as $key => $value) {
                    $wherestr[] = "`{$key}` = '{$value}' ";
                }
                $wherestr = implode(" {$connector} ", $wherestr);
            } else {
                $wherestr = $where;
            }
            $sql = "DELETE FROM {$table} WHERE {$wherestr}";
            $this->query($sql, "delete");
            return $this->affected_rows();
        } else {
            return false;
        }
    }

    /**
     * 更新数据
     * @param string $table 表名
     * @param mixed $value 数据，格式为：array('filed1'=>'value1')或array('field3'=>'#+1')或"filed='value1'"
     * @param mixed $where 条件，格式为：array('filed1'=>'value1')或"filed='value1'"
     * @param string $connector 连接符，默认为AND，可选AND或OR，该值只对$where为数组时有效
     * @return minxed 失败时返回false,成功时返回影响的条数
     */
    function update($table, $value = array(), $where = array(), $connector = 'AND') {
        if (count($where) >= 1) {
            $wherestr = $valuestr = array();
            if (is_array($value)) {
                foreach ($value as $field => $data) {
                    if (substr($data, 0, 1) == '#') {
                        $second = substr($data, 1, 1);
                        if (in_array($second, array('+', '-'))) {
                            $op = $second;
                            $data = substr($data, 2);
                        }
                    }
                    if (isset($op)) {
                        $valuestr[] = "`{$field}`=`{$field}` {$op} '{$data}'";
                        unset($op);
                    } else {
                        $valuestr[] = "`{$field}` = '{$data}' ";
                    }
                }
                $valuestr = implode(' , ', $valuestr);
            } else {
                $valuestr = $value;
            }

            if (is_array($where)) {
                foreach ($where as $field => $value) {
                    $wherestr[] = "`{$field}` = '{$value}' ";
                }
                $wherestr = implode(" {$connector} ", $wherestr);
            } else {
                $wherestr = $where;
            }
            $sql = "UPDATE {$table} SET {$valuestr} WHERE {$wherestr}";
            $this->query($sql, "update");
            return $this->affected_rows();
        } else {
            return false;
        }
    }

    /**
     * 获取指定标识的一条数组数据
     * @param string $id SQL语句执行的标识
     * @param constant $type 返回结果类型，默认为MYSQL_ASSOC,可接受MYSQL_BOTH，MYSQL_NUM
     * @return mixed 成功时返回数组，失败时返回false
     */
    function get_array($id = 'default', $type = MYSQL_ASSOC) {
        if ($this->result[$id] == 0) {
            return FALSE;
        } else {
            $row = mysql_fetch_array($this->result[$id], $type);
            get_int_type($row);
            return $row;
        }
    }

    /**
     * 获取指定标识的一条对象数据
     * @param string $id SQL语句执行的标识
     * @param constant $type 返回结果类型，默认为MYSQL_ASSOC,可接受MYSQL_BOTH，MYSQL_NUM
     * @return mixed 成功时返回对象，失败时返回false
     */
    function get_object($id = 'default') {
        return ($this->result[$id] == 0) ? false : mysql_fetch_object($this->result[$id]);
    }

    function colorsql($sql) {
        $reg = array(
            "/(['|`]+[a-zA-Z0-9_]+['|`]+)/",
            '/\b([0-9]+)\b/',
            '/(select|insert|drop|alter|create|show|execute|truncate|update|delete|replace)(\b|\()/i',
            '/(\b|\))(as|from|limit|into|values|index|left|right|join|on|using|table|status|group|by|having|order|asc|desc|like|max|min|sum|not|null|or|and|between|with|distinct|in|timestamp|union|set|count|where)(\b|\()/i',
        );
        $rep = array('<font color="green">\\1</font>', '<font color="red">\\1</font>', '<font color="blue">\\1</font>', '<font color="blue">\\2</font>');
        return preg_replace($reg, $rep, $sql);
    }

    /**
     * 转义SQL语句，替换前缀，过滤非法的关键字
     * @param string $sql SQL语句
     */
    function escape($sql) {
        $sql = str_replace(array("#PRE#", "{pre}"), $this->prefix, $sql);
        if ($this->safequery)
            $sql = preg_replace("/(union|benchmark|load_file|outfile|information_schema)/i", '', $sql);
        $this->sql = $sql;
        return $sql;
    }

    /**
     * 转义特殊字符
     * @param mixed $value
     * @return string
     */
    function prepare($value = array()) {
        if (is_array($value)) {
            foreach ($value as $m => $n) {
                $value[$m] = mysql_real_escape_string($n);
            }
        } else {
            $value = mysql_real_escape_string($value);
        }
        return $value;
    }

    /**
     * 简单综合查询
     * @param string $sql SQL语句
     * @param bool $result 是否返回影响记录数或执行结果
     * @param constant $type 是以何种方式返回记录集(MYSQL_ASSOC-关联索引、MYSQL_NUM-数字索引、MYSQL_BOTH-两者)
     * @return mixed 成功时返回影响记录数，失败时返回null
     */
    function simple($sql, $result = true, $id = 'default', $type = MYSQL_ASSOC) {
        $this->query($sql, $id);
        if (strtolower(substr($sql, 0, 6)) == 'select' && $result) {
            return $this->row($id, $type);
        } else if ($result) {
            return mysql_affected_rows($this->linkid);
        } else {
            return null;
        }
    }

    /**
     * 获取最后插入的数据ID
     * @return int 返回最后插入的ID
     */
    function insert_id() {
        return mysql_insert_id($this->linkid);
    }

    /**
     * 获取影响记录数
     * @return int 获取影响的记录数，如update,delete查询
     */
    function affected_rows() {
        return mysql_affected_rows($this->linkid);
    }

    /**
     * 获取记录总数
     * @return int 获取SELECT查询记录总数
     */
    function num_rows($id = 'default') {
        return ($this->result[$id] == 0) ? 0 : mysql_num_rows($this->result[$id]);
    }

    /**
     * 获取特定表的信息
     * @param string $tablename 表名
     * @param string $id 标识
     */
    function get_table_fields($tablename, $id = "default") {
        $tablename = $this->escape($tablename);
        $this->result[$id] = mysql_list_fields($this->name, $tablename, $this->linkid);
    }

    /**
     * 获取字段详细信息
     * @param string $id 标识
     * @return array
     */
    function get_field_object($id = "default") {
        return mysql_fetch_field($this->result[$id]);
    }

    /**
     * 释放指定标识的资源
     * @param int $id SQL查询的标识
     * @return bool 成功时返回true，失败时返回false
     */
    function free($id = 'default') {
        return mysql_free_result($this->result[$id]);
    }

    /**
     * 释放全部
     */
    function free_all() {
        if (is_array($this->result)) {
            foreach ($this->result as $key => $value) {
                if ($value)
                    @mysql_free_result($value);
            }
        }
    }

    /**
     * 抛出错误
     * @param string $msg 错误数据
     */
    function error($msg) {
        throw new Exception($msg . $this->sql);
    }

}